Context
大三时候做了精益防伪的第一代防伪芯片的Android版测试读写器。如今这个公司发展的有点儿好,所以,
他们出了第二代芯片,增加了更多安全措施。
为了对第二代芯片进行生产测试,我们需要做一个全新的测试软件。那么为了与新一代的RFID芯片进行通信,我们需要写一个底层驱动,来使得外接的USB
RFID读写器工作。
这个测试上位机是工作在windows上的,对于一个有强迫症的开发者来说,当时不会选择使用有跨平台特性的java来开发。
我们要了上一代芯片的测试程序,是用C++做的通信和界面(MFC)。然而用C++写界面,肯定是很费劲的。所以我们换个思路。充分利用windows上面可以调用各种dll的特点来解决c++写界面要累死
这种尴尬的情况。
这就会引出第一个坑(逐渐远离标题)
C#与C++混合编程
C++有多难写,应该不用再多说了,我就是那种不到万不得己,是绝对不会去碰C++的垃圾~所以得到了上一版测试程序后,我们看到了之前的RFID读写器驱动代码,将它进行生成,得到了一个dll。
在这个基础上,再封装一个wpf可以使用的库,那简直完美。
于是就直接新建一个C#的库项目,导入之前的底层项目,引入lib文件,(再花了很多精力解决了一些环境路径之类的问题),终于可以成功生成,在新建的wpf项目中添加引用:
错误:不是一个有效的COM组件!?
于是查了这个错误报告,有的说得用CLR库,就是公共语言运行库,想想好像也有道理。于是又如法炮制了一遍,终于可以成功生成了,再次添加引用:
错误:不兼容!?
于是又去查了一些资料,看到C#和C++混合编程,可以使用DllImport来导入C++ Dll中的函数,不过问题来了,这里在使用DllImport的时候,是需要做一些类型转换的。
毕竟.NET里面的一些数据类型,在C++中是没有的,所以从原理上来想,如果不做一些类型转换或者类型的重新定义的话,操作系统会很迷惑的~
所以,我们就放弃了使用原始C++库这个思路,准备撩起袖子自己写了。(逐渐想起标题)
USB
既然是重新开始,那就要开始疯狂的查询资料了。之前写过windows的串口通信。所以在我的想象中,他们应该是差不多的吧,把USB口子打开,就得到了input和output的流,之后就可以开开兴兴的进行通信了。
不过,并不是这样的~
作为一个懒惰的程序员,我们下一步想到的,就是找开源库。因为这个项目的时间有点紧张,所以怎么搞得快就得怎么做!
LibUsbDotNet
首先我们找到了LibUsbDotNet,仔细研究了他的一些接口,写了一套链接,发送的方法。
USB里面有一些末端(EndPoint)描述,这些末端的ID的第一个bit代表着这个末端的通信方向。如果第一个bit为1,则代表input,如果这个bit为0,则代表output。
public bool connectDevice() {
UsbRegDeviceList registryList= UsbDevice.AllDevices;
for (int index = 0; index < registryList.Count; index++) {
UsbRegistry registy = registryList[index];
if (registy.SymbolicName.Contains("5750") && registy.SymbolicName.Contains("0483"))
{
bool connect_status= registy.Open(out this.device);
if (connect_status) {
foreach (UsbConfigInfo info in this.device.Configs){
foreach (UsbInterfaceInfo interfaceInfo in info.InterfaceInfoList) {
foreach (UsbEndpointInfo endPoint in interfaceInfo.EndpointInfoList) {
System.Console.WriteLine(" id:" + endPoint.Descriptor.EndpointID);
if ((endPoint.Descriptor.EndpointID & 0x80) == 0x80)
{
//in
this.reader = this.device.OpenEndpointReader((ReadEndpointID)endPoint.Descriptor.EndpointID);
continue;
}
else {
//out
this.writer = this.device.OpenEndpointWriter((WriteEndpointID)endPoint.Descriptor.EndpointID);
continue;
}
}
}
}
if (this.writer != null && this.reader != null)
{
return true;
}
else {
return false;
}
}
}
}
return false;
}
缩进问题请谅解,垃圾visual studio
在学习使用lib-usb的时候,我们了解到了pid和vid。就和串口一样有com1,com2等。上位机(操作系统)通过pid和vid来过滤插入的硬件。
但是,我们发现使用lib usb的工作原理貌似不是这样的。感觉他会建立虚拟硬件,虚拟硬件建立完毕后,他的pid和vid都和原来的不一样了,但是好在他的guid里面还是包含了原始的pid和vid,所以采用了上面代码中的方法来过滤来设备。
有了writer和reader之后,对他们进行字节流操作,就ok了。
但是,不行。
还需要设置读入写出的handler,也就是说每一个I/O操作都强制需要有回调函数,否则不会成功调用。
而且当青青成功的使用lib-usb之后,发现做一次写入操作之后,整个软件就卡住了。连问题都没有找到~
Spidey大佬跟我说,usb是仅仅个介质,并不是一种通信。就好比说Wi-Fi是一种介质,真正通信的对象是服务器或者主机
Hid
HID也叫做人体输入学设备,打开windows的设备管理器,插上鼠标键盘就会显示一个人体输入学设备。
一开始用了一个叫HidLibrary的库,没成功,所以也没有做什么记录。
之后我们恍然大悟!一开始就不应该去学习USB开发,而应该去学习HID的开发!
HID的资料就比较多了~
接下来的内容转自于一位前辈大佬,很多的博文也是转载于他的文章。
Windows中对HID的操作比较好理解,和操作系统处理多线程服务器差不多,每个连接都用一个文件来维持。
我对他的代码做了修改,原来版本是异步的发送和接收,这样的做法很高效而且也很健康,只是我们这次的项目似乎用不到这么高级的方法。所以我将它改为了同步发送和同步接收。
这个项目是由HID类和HIDInterface类构成
现在还是直接贴代码吧
HID类:
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Windows;
namespace ReaderCSharp
{
public class Hid : object
{
private IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private const int MAX_USB_DEVICES = 64;
private bool deviceOpened = false;
private FileStream hidDevice = null;
private IntPtr hHubDevice;
/// <summary>
/// 打开指定信息的设备
/// </summary>
/// <param name="vID">设备的vID</param>
/// <param name="pID">设备的pID</param>
/// <param name="serial">设备的serial</param>
/// <returns></returns>
public HID_RETURN OpenDevice(UInt16 vID, UInt16 pID, string serial)
{
if (deviceOpened == false)
{
//获取连接的HID列表
List<string> deviceList = new List<string>();
GetHidDeviceList(ref deviceList);
if (deviceList.Count == 0)
return HID_RETURN.NO_DEVICE_CONECTED;
for (int i = 0; i < deviceList.Count; i++)
{
if (!(deviceList[i].Contains(String.Format("{0:X0000}", vID)) && deviceList[i].Contains(String.Format("{0:X0000}", pID)))) {
continue;
}
IntPtr device = CreateFile(deviceList[i],
0xc0000000,//DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE,
0x00000003,
0,
3,//CREATIONDISPOSITION.OPEN_EXISTING,
0x40000000,//FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED,
0);
if (device != INVALID_HANDLE_VALUE)
{
HIDD_ATTRIBUTES attributes;
IntPtr serialBuff = Marshal.AllocHGlobal(512);
HidD_GetAttributes(device, out attributes);
HidD_GetSerialNumberString(device, serialBuff, 512);
string deviceStr = Marshal.PtrToStringAuto(serialBuff);
Marshal.FreeHGlobal(serialBuff);
if (attributes.VendorID == vID && attributes.ProductID == pID && deviceStr.Contains(serial))
{
IntPtr preparseData;
HIDP_CAPS caps;
HidD_GetPreparsedData(device, out preparseData);
HidP_GetCaps(preparseData, out caps);
HidD_FreePreparsedData(preparseData);
//input length ==64
hidDevice = new FileStream(new SafeFileHandle(device, false), FileAccess.ReadWrite, 65, true);
deviceOpened = true;
//BeginAsyncRead();
hHubDevice = device;
return HID_RETURN.SUCCESS;
}
}
}
return HID_RETURN.DEVICE_NOT_FIND;
}
else
return HID_RETURN.DEVICE_OPENED;
}
/// <summary>
/// 关闭打开的设备
/// </summary>
public void CloseDevice()
{
if (deviceOpened == true)
{
deviceOpened = false;
try
{
hidDevice.Close();
}
catch (Exception e) {
System.Console.WriteLine(e.Message);
System.Console.WriteLine(e.StackTrace);
}
}
}
/// <summary>
/// 开始一次异步读
/// </summary>
public void ReadCMD()
{
byte[] inputBuff = new byte[65];
int offset = 0;
int total_size = 0;
int read_size = 0;
try
{
while (total_size < 65&&(read_size=hidDevice.Read(inputBuff, offset, 65 - offset)) > 0)
{
total_size += read_size;
}
if (total_size >= 65) {
OnDataReceived(inputBuff);
}
}
catch {
EventArgs ex = new EventArgs();
OnDeviceRemoved(ex);//发出设备移除消息
CloseDevice();
}
}
public delegate void DelegateDataReceived(object sender, byte[] e);
//public event EventHandler<ConnectEventArg> StatusConnected;
public DelegateDataReceived DataReceived;
/// <summary>
/// 事件:数据到达,处理此事件以接收输入数据
/// </summary>
protected virtual void OnDataReceived(byte[] e)
{
if (DataReceived != null) DataReceived(this, e);
}
/// <summary>
/// 事件:设备断开
/// </summary>
public delegate void DelegateStatusConnected(object sender, EventArgs e);
public DelegateStatusConnected DeviceRemoved;
protected virtual void OnDeviceRemoved(EventArgs e)
{
if (DeviceRemoved != null) DeviceRemoved(this, e);
}
/// <summary>
///
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public HID_RETURN Write(byte[] r)
{
if (deviceOpened)
{
if (!hidDevice.CanWrite) {
return HID_RETURN.WRITE_FAILD;
}
try
{
byte[] buffer = new byte[65];
buffer[0] = 0;
for (int i = 0; i < 64; i++)
buffer[i+1] = r[i];
hidDevice.Write(buffer, 0, 65);
return HID_RETURN.SUCCESS;
}
catch (IOException exception)
{
System.Console.WriteLine(exception.Message);
System.Console.WriteLine(exception.StackTrace.ToString());
EventArgs ex = new EventArgs();
OnDeviceRemoved(ex);//发出设备移除消息
CloseDevice();
return HID_RETURN.NO_DEVICE_CONECTED;
}
}
return HID_RETURN.WRITE_FAILD;
}
/// <summary>
/// 获取所有连接的hid的设备路径
/// </summary>
/// <returns>包含每个设备路径的字符串数组</returns>
public static void GetHidDeviceList(ref List<string> deviceList)
{
Guid hUSB = Guid.Empty;
uint index = 0;
deviceList.Clear();
// 取得hid设备全局id
HidD_GetHidGuid(ref hUSB);
//取得一个包含所有HID接口信息集合的句柄
IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);
if (hidInfoSet != IntPtr.Zero)
{
SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);
//查询集合中每一个接口
for (index = 0; index < MAX_USB_DEVICES; index++)
{
//得到第index个接口信息
if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo))
{
int buffsize = 0;
// 取得接口详细信息:第一次读取错误,但可以取得信息缓冲区的大小
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
//构建接收缓冲
IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
SP_DEVICE_INTERFACE_DETAIL_DATA detail = new SP_DEVICE_INTERFACE_DETAIL_DATA();
detail.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
Marshal.StructureToPtr(detail, pDetail, false);
if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null))
{
deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4)));
}
Marshal.FreeHGlobal(pDetail);
}
}
}
SetupDiDestroyDeviceInfoList(hidInfoSet);
//return deviceList.ToArray();
}
#region<连接USB返回的结构体信息>
/// <summary>
/// 连接USB返回的结构体信息
/// </summary>
public enum HID_RETURN
{
SUCCESS = 0,
NO_DEVICE_CONECTED,
DEVICE_NOT_FIND,
DEVICE_OPENED,
WRITE_FAILD,
READ_FAILD
}
#endregion
// 以下是调用windows的API的函数
/// <summary>
/// The HidD_GetHidGuid routine returns the device interface GUID for HIDClass devices.
/// </summary>
/// <param name="HidGuid">a caller-allocated GUID buffer that the routine uses to return the device interface GUID for HIDClass devices.</param>
[DllImport("hid.dll")]
private static extern void HidD_GetHidGuid(ref Guid HidGuid);
/// <summary>
/// The SetupDiGetClassDevs function returns a handle to a device information set that contains requested device information elements for a local machine.
/// </summary>
/// <param name="ClassGuid">GUID for a device setup class or a device interface class. </param>
/// <param name="Enumerator">A pointer to a NULL-terminated string that supplies the name of a PnP enumerator or a PnP device instance identifier. </param>
/// <param name="HwndParent">A handle of the top-level window to be used for a user interface</param>
/// <param name="Flags">A variable that specifies control options that filter the device information elements that are added to the device information set. </param>
/// <returns>a handle to a device information set </returns>
[DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
/// <summary>
/// The SetupDiDestroyDeviceInfoList function deletes a device information set and frees all associated memory.
/// </summary>
/// <param name="DeviceInfoSet">A handle to the device information set to delete.</param>
/// <returns>returns TRUE if it is successful. Otherwise, it returns FALSE </returns>
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr deviceInfoSet);
/// <summary>
/// The SetupDiEnumDeviceInterfaces function enumerates the device interfaces that are contained in a device information set.
/// </summary>
/// <param name="deviceInfoSet">A pointer to a device information set that contains the device interfaces for which to return information</param>
/// <param name="deviceInfoData">A pointer to an SP_DEVINFO_DATA structure that specifies a device information element in DeviceInfoSet</param>
/// <param name="interfaceClassGuid">a GUID that specifies the device interface class for the requested interface</param>
/// <param name="memberIndex">A zero-based index into the list of interfaces in the device information set</param>
/// <param name="deviceInterfaceData">a caller-allocated buffer that contains a completed SP_DEVICE_INTERFACE_DATA structure that identifies an interface that meets the search parameters</param>
/// <returns></returns>
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
/// <summary>
/// The SetupDiGetDeviceInterfaceDetail function returns details about a device interface.
/// </summary>
/// <param name="deviceInfoSet">A pointer to the device information set that contains the interface for which to retrieve details</param>
/// <param name="deviceInterfaceData">A pointer to an SP_DEVICE_INTERFACE_DATA structure that specifies the interface in DeviceInfoSet for which to retrieve details</param>
/// <param name="deviceInterfaceDetailData">A pointer to an SP_DEVICE_INTERFACE_DETAIL_DATA structure to receive information about the specified interface</param>
/// <param name="deviceInterfaceDetailDataSize">The size of the DeviceInterfaceDetailData buffer</param>
/// <param name="requiredSize">A pointer to a variable that receives the required size of the DeviceInterfaceDetailData buffer</param>
/// <param name="deviceInfoData">A pointer buffer to receive information about the device that supports the requested interface</param>
/// <returns></returns>
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, IntPtr deviceInterfaceDetailData, int deviceInterfaceDetailDataSize, ref int requiredSize, SP_DEVINFO_DATA deviceInfoData);
/// <summary>
/// The HidD_GetAttributes routine returns the attributes of a specified top-level collection.
/// </summary>
/// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param>
/// <param name="Attributes">a caller-allocated HIDD_ATTRIBUTES structure that returns the attributes of the collection specified by HidDeviceObject</param>
/// <returns></returns>
[DllImport("hid.dll")]
private static extern Boolean HidD_GetAttributes(IntPtr hidDeviceObject, out HIDD_ATTRIBUTES attributes);
/// <summary>
/// The HidD_GetSerialNumberString routine returns the embedded string of a top-level collection that identifies the serial number of the collection's physical device.
/// </summary>
/// <param name="HidDeviceObject">Specifies an open handle to a top-level collection</param>
/// <param name="Buffer">a caller-allocated buffer that the routine uses to return the requested serial number string</param>
/// <param name="BufferLength">Specifies the length, in bytes, of a caller-allocated buffer provided at Buffer</param>
/// <returns></returns>
[DllImport("hid.dll")]
private static extern Boolean HidD_GetSerialNumberString(IntPtr hidDeviceObject, IntPtr buffer, int bufferLength);
/// <summary>
/// The HidD_GetPreparsedData routine returns a top-level collection's preparsed data.
/// </summary>
/// <param name="hidDeviceObject">Specifies an open handle to a top-level collection. </param>
/// <param name="PreparsedData">Pointer to the address of a routine-allocated buffer that contains a collection's preparsed data in a _HIDP_PREPARSED_DATA structure.</param>
/// <returns>HidD_GetPreparsedData returns TRUE if it succeeds; otherwise, it returns FALSE.</returns>
[DllImport("hid.dll")]
private static extern Boolean HidD_GetPreparsedData(IntPtr hidDeviceObject, out IntPtr PreparsedData);
[DllImport("hid.dll")]
private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);
[DllImport("hid.dll")]
private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
/// <summary>
/// This function creates, opens, or truncates a file, COM port, device, service, or console.
/// </summary>
/// <param name="fileName">a null-terminated string that specifies the name of the object</param>
/// <param name="desiredAccess">Type of access to the object</param>
/// <param name="shareMode">Share mode for object</param>
/// <param name="securityAttributes">Ignored; set to NULL</param>
/// <param name="creationDisposition">Action to take on files that exist, and which action to take when files do not exist</param>
/// <param name="flagsAndAttributes">File attributes and flags for the file</param>
/// <param name="templateFile">Ignored</param>
/// <returns>An open handle to the specified file indicates success</returns>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateFile(string fileName, uint desiredAccess, uint shareMode, uint securityAttributes, uint creationDisposition, uint flagsAndAttributes, uint templateFile);
/// <summary>
/// This function closes an open object handle.
/// </summary>
/// <param name="hObject">Handle to an open object</param>
/// <returns></returns>
[DllImport("kernel32.dll")]
private static extern int CloseHandle(IntPtr hObject);
/// <summary>
/// This function reads data from a file, starting at the position indicated by the file pointer.
/// </summary>
/// <param name="file">Handle to the file to be read</param>
/// <param name="buffer">Pointer to the buffer that receives the data read from the file </param>
/// <param name="numberOfBytesToRead">Number of bytes to be read from the file</param>
/// <param name="numberOfBytesRead">Pointer to the number of bytes read</param>
/// <param name="lpOverlapped">Unsupported; set to NULL</param>
/// <returns></returns>
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool ReadFile(IntPtr file, byte[] buffer, uint numberOfBytesToRead, out uint numberOfBytesRead, IntPtr lpOverlapped);
/// <summary>
/// This function writes data to a file
/// </summary>
/// <param name="file">Handle to the file to be written to</param>
/// <param name="buffer">Pointer to the buffer containing the data to write to the file</param>
/// <param name="numberOfBytesToWrite">Number of bytes to write to the file</param>
/// <param name="numberOfBytesWritten">Pointer to the number of bytes written by this function call</param>
/// <param name="lpOverlapped">Unsupported; set to NULL</param>
/// <returns></returns>
[DllImport("Kernel32.dll", SetLastError = true)]
private static extern bool WriteFile(IntPtr file, byte[] buffer, uint numberOfBytesToWrite, out uint numberOfBytesWritten, IntPtr lpOverlapped);
/// <summary>
/// Registers the device or type of device for which a window will receive notifications
/// </summary>
/// <param name="recipient">A handle to the window or service that will receive device events for the devices specified in the NotificationFilter parameter</param>
/// <param name="notificationFilter">A pointer to a block of data that specifies the type of device for which notifications should be sent</param>
/// <param name="flags">A Flags that specify the handle type</param>
/// <returns>If the function succeeds, the return value is a device notification handle</returns>
[DllImport("User32.dll", SetLastError = true)]
private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);
/// <summary>
/// Closes the specified device notification handle.
/// </summary>
/// <param name="handle">Device notification handle returned by the RegisterDeviceNotification function</param>
/// <returns></returns>
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterDeviceNotification(IntPtr handle);
}
#region
/// <summary>
/// SP_DEVICE_INTERFACE_DATA structure defines a device interface in a device information set.
/// </summary>
public struct SP_DEVICE_INTERFACE_DATA
{
public int cbSize;
public Guid interfaceClassGuid;
public int flags;
public int reserved;
}
/// <summary>
/// SP_DEVICE_INTERFACE_DETAIL_DATA structure contains the path for a device interface.
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 2)]
internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
internal int cbSize;
internal short devicePath;
}
/// <summary>
/// SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class SP_DEVINFO_DATA
{
public int cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA));
public Guid classGuid = Guid.Empty; // temp
public int devInst = 0; // dumy
public int reserved = 0;
}
/// <summary>
/// Flags controlling what is included in the device information set built by SetupDiGetClassDevs
/// </summary>
public enum DIGCF
{
DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE
DIGCF_PRESENT = 0x00000002,
DIGCF_ALLCLASSES = 0x00000004,
DIGCF_PROFILE = 0x00000008,
DIGCF_DEVICEINTERFACE = 0x00000010
}
/// <summary>
/// The HIDD_ATTRIBUTES structure contains vendor information about a HIDClass device
/// </summary>
public struct HIDD_ATTRIBUTES
{
public int Size;
public ushort VendorID;
public ushort ProductID;
public ushort VersionNumber;
}
public struct HIDP_CAPS
{
public ushort Usage;
public ushort UsagePage;
public ushort InputReportByteLength;
public ushort OutputReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
public ushort[] Reserved;
public ushort NumberLinkCollectionNodes;
public ushort NumberInputButtonCaps;
public ushort NumberInputValueCaps;
public ushort NumberInputDataIndices;
public ushort NumberOutputButtonCaps;
public ushort NumberOutputValueCaps;
public ushort NumberOutputDataIndices;
public ushort NumberFeatureButtonCaps;
public ushort NumberFeatureValueCaps;
public ushort NumberFeatureDataIndices;
}
/// <summary>
/// Type of access to the object.
///</summary>
static class DESIREDACCESS
{
public const uint GENERIC_READ = 0x80000000;
public const uint GENERIC_WRITE = 0x40000000;
public const uint GENERIC_EXECUTE = 0x20000000;
public const uint GENERIC_ALL = 0x10000000;
}
/// <summary>
/// Action to take on files that exist, and which action to take when files do not exist.
/// </summary>
static class CREATIONDISPOSITION
{
public const uint CREATE_NEW = 1;
public const uint CREATE_ALWAYS = 2;
public const uint OPEN_EXISTING = 3;
public const uint OPEN_ALWAYS = 4;
public const uint TRUNCATE_EXISTING = 5;
}
/// <summary>
/// File attributes and flags for the file.
/// </summary>
static class FLAGSANDATTRIBUTES
{
public const uint FILE_FLAG_WRITE_THROUGH = 0x80000000;
public const uint FILE_FLAG_OVERLAPPED = 0x40000000;
public const uint FILE_FLAG_NO_BUFFERING = 0x20000000;
public const uint FILE_FLAG_RANDOM_ACCESS = 0x10000000;
public const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;
public const uint FILE_FLAG_DELETE_ON_CLOSE = 0x04000000;
public const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public const uint FILE_FLAG_POSIX_SEMANTICS = 0x01000000;
public const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;
public const uint FILE_FLAG_OPEN_NO_RECALL = 0x00100000;
public const uint FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000;
}
/// <summary>
/// Serves as a standard header for information related to a device event reported through the WM_DEVICECHANGE message.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HDR
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
}
/// <summary>
/// Contains information about a class of devices
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DEV_BROADCAST_DEVICEINTERFACE
{
public int dbcc_size;
public int dbcc_devicetype;
public int dbcc_reserved;
public Guid dbcc_classguid;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string dbcc_name;
}
#endregion
}
HID类是最底层的,他直接使用了window API,这是一个C++的库,所以不能直接引用,需要使用前文提到的DllImport来导入并翻译。
HIDInterface类:
using System;
using System.Text;
using System.Threading;
using System.IO;
using System.ComponentModel;
namespace ReaderCSharp
{
public class HIDInterface : IDisposable
{
public enum MessagesType
{
Message,
Error
}
public struct ReusltString
{
public bool Result;
public string message;
}
public struct HidDevice
{
public UInt16 vID;
public UInt16 pID;
public string serial;
}
HidDevice lowHidDevice = new HidDevice();
public delegate void DelegateDataReceived(object sender, byte[] data);
public DelegateDataReceived DataReceived;
public delegate void DelegateStatusConnected(object sender, bool isConnect);
public DelegateStatusConnected StatusConnected;
public bool bConnected = false;
public Hid oSp = new Hid();
private static HIDInterface m_oInstance;
public struct TagInfo
{
public string AntennaPort;
public string EPC;
}
public HIDInterface()
{
m_oInstance = this;
oSp.DataReceived = HidDataReceived;
oSp.DeviceRemoved = HidDeviceRemoved;
}
protected virtual void RaiseEventConnectedState(bool isConnect)
{
if (null != StatusConnected) StatusConnected(this, isConnect);
}
protected virtual void RaiseEventDataReceived(byte[] buf)
{
if (null != DataReceived) DataReceived(this, buf);
}
public void AutoConnect(HidDevice hidDevice)
{
lowHidDevice = hidDevice;
ContinueConnectFlag = true;
ReadWriteThread.DoWork += ReadWriteThread_DoWork;
ReadWriteThread.WorkerSupportsCancellation = true;
ReadWriteThread.RunWorkerAsync(); //Recommend performing USB read/write operations in a separate thread. Otherwise,
}
public void StopAutoConnect()
{
try
{
ContinueConnectFlag = false;
Dispose();
}
catch
{
}
}
~HIDInterface()
{
Dispose();
}
public bool Connect(HidDevice hidDevice)
{
ReusltString result = new ReusltString();
Hid.HID_RETURN hdrtn = oSp.OpenDevice(hidDevice.vID, hidDevice.pID, hidDevice.serial);
if (hdrtn == Hid.HID_RETURN.SUCCESS)
{
bConnected = true;
#region 消息通知
result.Result = true;
result.message = "Connect Success!";
RaiseEventConnectedState(result.Result);
#endregion
return true;
}
bConnected = false;
#region 消息通知
result.Result = false;
result.message = "Device Connect Error";
RaiseEventConnectedState(result.Result);
#endregion
return false;
}
public bool Send(byte[] byData)
{
byte[] sendtemp = new byte[64];
Array.Copy(byData, 0, sendtemp, 0, byData.Length>64?64:byData.Length);
Hid.HID_RETURN hdrtn = oSp.Write(sendtemp);
if (hdrtn != Hid.HID_RETURN.SUCCESS)
{
return false;
}
return true;
}
public bool Send(string strData)
{
//获得报文的编码字节
byte[] data = Encoding.Unicode.GetBytes(strData);
return Send(data);
}
public bool Read()
{
if (bConnected == false) {
return false;
}
oSp.ReadCMD();
return true;
}
public void DisConnect()
{
bConnected = false;
Thread.Sleep(200);
if (oSp != null)
{
oSp.CloseDevice();
}
}
void HidDeviceRemoved(object sender, EventArgs e)
{
bConnected = false;
#region 消息通知
ReusltString result = new ReusltString();
result.Result = false;
result.message = "Device Remove";
RaiseEventConnectedState(result.Result);
#endregion
if (oSp != null)
{
oSp.CloseDevice();
}
}
public void HidDataReceived(object sender, byte[] e)
{
try
{
//第一个字节为数据长度,因为Device 的HID数据固定长度为64字节,取有效数据
int length = 64;
byte[] buf = new byte[length];
Array.Copy(e, 1, buf, 0, length);
//推送数据
RaiseEventDataReceived(buf);
}
catch
{
#region 消息通知
ReusltString result = new ReusltString();
result.Result = false;
result.message = "Receive Error";
RaiseEventConnectedState(result.Result);
#endregion
}
}
public void Dispose()
{
try
{
this.DisConnect();
oSp.DataReceived -= HidDataReceived;
oSp.DeviceRemoved -= HidDeviceRemoved;
ReadWriteThread.DoWork -= ReadWriteThread_DoWork;
ReadWriteThread.CancelAsync();
ReadWriteThread.Dispose();
}
catch
{ }
}
Boolean ContinueConnectFlag = true;
}
}
使用起来非常方便,只要使用HIDInterface类就可以了,而且自动重连的功能还保留着。
使用方法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ReaderCSharp
{
public class TagReaderManager
{
private static TagReaderManager shared;
struct connectStatusStruct
{
public bool preStatus;
public bool curStatus;
}
connectStatusStruct connectStatus = new connectStatusStruct();
//推送连接状态信息
public delegate void isConnectedDelegate(bool isConnected);
public isConnectedDelegate isConnectedFunc;
//推送接收数据信息
public delegate void PushReceiveDataDele(byte[] datas);
public PushReceiveDataDele pushReceiveData;
private byte[] receivedBuffer = null;
private HIDInterface hid;
public static TagReaderManager sharedManager()
{
if (shared == null) {
shared = new TagReaderManager();
}
return shared;
}
private TagReaderManager()
{
hid = new HIDInterface();
}
private bool sendingData(byte[] data) {
return hid.Send(data);
}
bool receiveData()
{
return hid.Read();
}
public bool connectDevice()
{
hid.StatusConnected = StatusConnected;
hid.DataReceived = DataReceived;
HIDInterface.HidDevice hidDevice = new HIDInterface.HidDevice();
hidDevice.vID = 0x0483;
hidDevice.pID = 0x5750;
hidDevice.serial = "";
hid.AutoConnect(hidDevice);
connectionStatus = false;
return true;
}
//接受到数据
public void DataReceived(object sender, byte[] e)
{
if (e != null) {
Console.WriteLine(BitConverter.ToString(e));
receivedBuffer = e;
}
}
//状态改变接收
public void StatusConnected(object sender, bool isConnect)
{
connectStatus.curStatus = isConnect;
if (connectStatus.curStatus == connectStatus.preStatus) //connect
return;
connectStatus.preStatus = connectStatus.curStatus;
if (connectStatus.curStatus)
{
connectionStatus = true;
System.Console.WriteLine("hid connected!");
//ReportMessage(MessagesType.Message, "连接成功");
}
else //disconnect
{
connectionStatus = false;
System.Console.WriteLine("hid disconnected!");
//ReportMessage(MessagesType.Error, "无法连接");
}
}
public bool connectionStatus{ set; get; }
}
}
发送就是发送,接收的话,通过一个flag和receivedBuffer来确定,很好理解~
HID的代码和原作者的代码相比较有一些修改,OpenDevice这个函数中CreateFile()函数的参数有些不同,原作者是禁止其他进程共享HID描述文件的。然而这样一来我们的程序就无法成功的建立文件,并进行通信。
所以我怀疑可能是操作系统版本不同,所以系统对于共享权限有了不同的限制吧~
End
这次通过这个项目,感觉自己对软硬件通信又长进了不少。将这几个HID相关的东西做了封装之后,其他同学调用起来操控读取器就方便多了,而且最重要的是,这个库完完全全是.NET的库,所以他们可以用wpf或者winform来写界面了,可以说是非常爽了。